package ch.webkito.servlet;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.ResourceBundle;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.IOUtils;

import ch.webkito.i18n.I18n;
import ch.webkito.template.Partial;
import ch.webkito.template.Partials;
import ch.webkito.template.Template;

public abstract class WebkitoServlet extends HttpServlet {

	private static final long serialVersionUID = 1L;
	
	public static final String HTML_EXTENSION = ".html";
	public static final String JSON_EXTENSION = ".json";
	public static final String XML_EXTENSION = ".xml";
	
	public static final String PARTIAL_EXTENSION = ".par";
	public static final String TEMPLATE_FILE_EXTENSION = ".st";
	
	// convention over configuration
	public static final String TEMPLATE_FOLDER = "WEB-INF/templates/";
	public static final String PARTIAL_FOLDER = "WEB-INF/partials/";
	public static final String I18N_FOLDER = "i18n/";
	
	public static final String CONTENT_TYPE_HTML = "text/html";
	public static final String CONTENT_TYPE_JSON = "application/json";
	public static final String CONTENT_TYPE_XML = "application/xml";
	
	private static final Map<String, Object> noParameter = new HashMap<String, Object>();
	
	protected void autoRender(HttpServletRequest request, HttpServletResponse response) throws IOException {
		autoRender(request, response, noParameter);
	}
	
	protected void autoRender(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params) throws IOException {
		if(request.getRequestURI().endsWith(HTML_EXTENSION)) {
			renderHtml(request, response, params);
		} else if(request.getRequestURI().endsWith(JSON_EXTENSION)) {
			renderJson(request, response, params);
		} else if(request.getRequestURI().endsWith(XML_EXTENSION)) {
			renderXml(request, response, params);
		}
	}
	/**
	 * writes the content of the template defined as init parameter TEMPLATE as repsonse
	 * and sets the content type to CONTENT_TYPE_HTML
	 * @param response
	 * @throws IOException
	 */
	protected void renderHtml(HttpServletRequest request, HttpServletResponse response) throws IOException {
		renderHtml(request, response, noParameter);
	}
	
	/**
	 * inserts the params into the template and writes the content of
	 * the template defined as @Template as repsonse
	 * and sets the content type to CONTENT_TYPE_HTML
	 * @param response
	 * @param params
	 * @throws IOException
	 */
	protected void renderHtml(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params) throws IOException {
		response.setContentType(CONTENT_TYPE_HTML);
		render(request, response, params, HTML_EXTENSION);
	}
	/**
	 * writes the content of the template defined as init parameter TEMPLATE as repsonse
	 * and sets the content type to CONTENT_TYPE_JSON
	 * @param response
	 * @throws IOException
	 */
	protected void renderJson(HttpServletRequest request, HttpServletResponse response) throws IOException {
		renderJson(request, response, noParameter);
	}
	
	/**
	 * inserts the params into the template and writes the content of
	 * the template defined as @Template as repsonse
	 * and sets the content type to CONTENT_TYPE_JSON
	 * @param response
	 * @param params
	 * @throws IOException
	 */
	protected void renderJson(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params) throws IOException {
		response.setContentType(CONTENT_TYPE_JSON);
		render(request, response, params, JSON_EXTENSION);
	}
	
	/**
	 * writes the content of the template defined as init parameter TEMPLATE as repsonse
	 * and sets the content type to CONTENT_TYPE_XML
	 * @param response
	 * @throws IOException
	 */
	protected void renderXml(HttpServletRequest request, HttpServletResponse response) throws IOException {
		renderXml(request, response, noParameter);
	}
	
	/**
	 * inserts the params into the template and writes the content of
	 * the template defined as @Template as repsonse
	 * and sets the content type to CONTENT_TYPE_XML
	 * @param response
	 * @param params
	 * @throws IOException
	 */
	protected void renderXml(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params) throws IOException {
		response.setContentType(CONTENT_TYPE_XML);
		render(request, response, params, XML_EXTENSION);
	}
	
	/**
	 * inserts the params into the template and writes the content of
	 * the template defined as @Template as repsonse
	 * @param response
	 * @param params
	 * @param fileExtension
	 * @throws IOException
	 * @throws RuntimeException if response and/or params are null
	 */
	protected void render(HttpServletRequest request, HttpServletResponse response, Map<String, Object> params, String fileExtension) throws IOException {
		if(response == null) throw new RuntimeException("response can not be null");
		if(params == null) throw new RuntimeException("params can not be null");
		
		StringTemplate st = stringTemplate(getTemplate() + fileExtension);
		if(getClass().isAnnotationPresent(I18n.class)) {
			/*
			 * example template:
			 * $welcomeuser$
			 * 
			 * $user$
			 * 
			 * example bundle:
			 * welcomeuser=welcome $user$
			 * 
			 * so we first replace $welcomeuser$ with the bundle value
			 * and $user$ (in the template) with the params (otherwise
			 * it will be blank!
			 */
			st.setArgumentContext(params);
			Partial[] partials = getPartials();
			StringTemplate[] partialStringTemplates = new StringTemplate[partials.length];
			for(int i = 0; i < partials.length; i++) {
				StringTemplate st2 = stringTemplate(partials[i].partial() + PARTIAL_EXTENSION, PARTIAL_FOLDER);
				st2.setArgumentContext(params);
				st.setAttribute(partials[i].name(), st2);
				partialStringTemplates[i] = st2;
				
			}
			for(String propertiesName : getClass().getAnnotation(I18n.class).value()) {
				ResourceBundle bundle = ResourceBundle.getBundle(I18N_FOLDER + propertiesName, request.getLocale());
				for(String key : bundle.keySet()) {
					st.setAttribute(key, bundle.getString(key));
					for(StringTemplate partialStringTemplate : partialStringTemplates) {
						partialStringTemplate.setAttribute(key, bundle.getString(key));
					}
				}
			}
			st = new StringTemplate(st.toString());
		}
		// and then replace $user$ with params
		st.setArgumentContext(params);
		response.getWriter().write(st.toString());
		response.getWriter().close();
		
	}

	private String getTemplate() {
		if(getClass().isAnnotationPresent(Template.class)) {
			return getClass().getAnnotation(Template.class).value();
		}
		throw new RuntimeException("Missing Template annotation for " + getClass().getName());
	}
	
	private Partial[] getPartials() {
		if(getClass().isAnnotationPresent(Partials.class)) {
			return getClass().getAnnotation(Partials.class).value(); 
		}
		
		return new Partial[0];
	}
	
	/**
	 * get a new instance of stringtemplate with templateName as template
	 * @param templateName template inside /WEB-INF/templates/ + templateName + .st
	 * @return new instance of stringtemplate with templateName as template
	 * @throws IOException
	 */
	protected StringTemplate stringTemplate(String templateName) throws IOException {
		return stringTemplate(templateName, TEMPLATE_FOLDER);
	}
	
	protected StringTemplate stringTemplate(String templateName, String folder) throws IOException {
		final InputStream stream = getServletContext().getResourceAsStream(folder + templateName + TEMPLATE_FILE_EXTENSION);
		return  new StringTemplate(IOUtils.toString(stream));
	}
}
